package com.gitee.wsl.struct.compare.diff.matcher


typealias Mapping<T> =  Pair<T,T>

/**
 * Class dedicated to store mappings between two ASTs.
 * Internally, the mappings are stored via two hashmaps.
 *
 * @see T
 */
open class MappingStore<T >(val src: T, val dst: T) : Iterable<Mapping<T>> {
    private val srcToDst: MutableMap<T, T> = HashMap()
    private val dstToSrc: MutableMap<T, T> = HashMap()

    /**
     * Instantiate a Mapping<T> store using the mappings of the provided
     * Mapping<T> store.
     * The references to the source and destination AST are also copied
     * from the provided Mapping<T> store.
     */
    constructor(ms: MappingStore<T>) : this(ms.src, ms.dst) {
        for (m in ms) addMapping(m.first, m.second)
    }

    /**
     * Return the number of mappings.
     */
    fun size(): Int {
        return srcToDst.size
    }

    /**
     * Converts the Mapping<T> store to a set of mappings.
     */
    fun asSet(): Set<Mapping<T>> {
        return object : AbstractSet<Mapping<T>>() {
            override val size: Int
                get() = srcToDst.keys.size

            override operator fun iterator(): Iterator<Mapping<T>> {
                val it: Iterator<T> = srcToDst.keys.iterator()
                return object : Iterator<Mapping<T>> {
                    override fun hasNext(): Boolean {
                        return it.hasNext()
                    }

                    override fun next(): Mapping<T> {
                        val src = it.next()
                        return src to srcToDst[src]!!
                    }
                }
            }
        }
    }

    /**
     * Add a Mapping<T> between the two provided nodes inside the Mapping<T> store.
     */
    fun addMapping(src: T, dst: T) {
        srcToDst[src] = dst
        dstToSrc[dst] = src
    }


    /**
     * Remove the Mapping<T> between the provided source and destination nodes.
     */
    fun removeMapping(src: T, dst: T) {
        srcToDst.remove(src)
        dstToSrc.remove(dst)
    }

    /**
     * Return the destination source node mapped to the given source node.
     * If there is no Mapping<T> involving the source node, null is returned.
     */
    fun getDstForSrc(src: T): T? {
        return srcToDst[src]
    }

    /**
     * Return the source node mapped to the given destination node.
     * If there is no Mapping<T> involving the destination node, null is returned.
     */
    fun getSrcForDst(dst: T): T? {
        return dstToSrc[dst]
    }

    /**
     * Return whether or not there is a Mapping<T> for the given source node.
     */
    fun isSrcMapped(src: T): Boolean {
        return srcToDst.containsKey(src)
    }

    /**
     * Return whether or not there is a Mapping<T> for the given destination node.
     */
    fun isDstMapped(dst: T): Boolean {
        return dstToSrc.containsKey(dst)
    }

    /**
     * Return whether or not the given source and destination nodes are unmapped.
     */
    fun areBothUnmapped(src: T, dst: T): Boolean {
        return !(isSrcMapped(src) || isDstMapped(dst))
    }

    /**
     * Return whether or not a Mapping<T> is possible between the provided source and
     * destination nodes.
     */
    /*fun isMappingAllowed(src: T, dst: T): Boolean {
        return src.hasSameType(dst) && areBothUnmapped(src, dst)
    }*/

    /**
     * Return whether or not all the given source nodes are unmapped.
     */
    fun areSrcsUnmapped(srcs: Collection<T>): Boolean {
        for (src in srcs) if (isSrcMapped(src)) return false
        return true
    }

    /**
     * Return whether or not all the given destination nodes are unmapped.
     */
    fun areDstsUnmapped(dsts: Collection<T>): Boolean {
        for (dst in dsts) if (isDstMapped(dst)) return false
        return true
    }

    /**
     * Returns whether or not there is a Mapping<T> between the given source and
     * destination nodes.
     */
    fun has(src: T, dst: T): Boolean {
        return srcToDst[src] === dst
    }

    override fun iterator(): Iterator<Mapping<T>> {
        return asSet().iterator()
    }

    override fun toString(): String {
        val b = StringBuilder()
        for (m in this) b.append(m.toString()).append('\n')
        return b.toString()
    }


    /**
     * Add a Mapping<T> between the two provided nodes inside the Mapping<T> store.
     * Mappings between all descendants of the two nodes are added as well.
     * The Ts rooted at the source and destination nodes are expected to be at
     * least iso structural.
     *
     * @see T.isIsoStructuralTo
     */
    /*fun addMappingRecursively(src: T, dst: T) {
        addMapping(src, dst)
        for (i in 0 until src.getChildren().size()) addMappingRecursively(
            src.getChild(i),
            dst.getChild(i)
        )
    }*/

    /**
     * Return whether or not the given source node has unmapped descendants.
     */
    /*fun hasUnmappedSrcChildren(t: T): Boolean {
        for (c in t.getDescendants()) if (!isSrcMapped(c)) return true
        return false
    }*/

    /**
     * Return whether or not the given destination node has unmapped descendants.
     */
    /*fun hasUnmappedDstChildren(t: T): Boolean {
        for (c in t.getDescendants()) if (!isDstMapped(c)) return true
        return false
    }
    */


}
